package com.yh.service.impl;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import com.yh.dao.*;
import com.yh.entity.*;
import com.yh.service.CinemaService;
import com.yh.service.MovieService;
import com.yh.utils.*;
import com.yh.vo.CinemaVO;
import com.yh.vo.CustomPageAndNavigate;
import com.yh.vo.HallVO;
import com.yh.vo.ScheduleVO;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.annotation.DubboService;
import org.apache.log4j.Logger;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.util.ObjectUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author yuhuan
 * @date 2022/01/13
 */
@DubboService
public class CinemaServiceImpl implements CinemaService {
    /**
     * 日志
     */
    private static final Logger logger = Logger.getLogger(CinemaServiceImpl.class);

    @Autowired
    private CinemaDao cinemaDao;
    @Autowired
    private BrandDao brandDao;
    @Autowired
    private ScheduleDao scheduleDao;
    @Autowired
    private HallDao hallDao;
    @Autowired
    private AdvantageDao advantageDao;
    @Autowired
    private LanguageVersionDao versionDao;
    @Autowired
    private HallTypeDao hallTypeDao;
    @Autowired
    private LayoutDao layoutDao;
    @Autowired
    private OrderDao orderDao;
    @Autowired
    private MovieDao movieDao;
    @DubboReference(check = false)
    private MovieService movieService;
    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public List<Brand> findCinemaBrandAll() {
        return brandDao.findBrandAll();
    }

    @Override
    public List<CinemaVO> findCinemaAll() {
        List<Cinema> cinemaList = cinemaDao.findCinemaAll();
        if (ObjectUtils.isEmpty(cinemaList)) {
            return new ArrayList<>();
        }
        List<CinemaVO> voList = copyCinemaToVo(cinemaList);
        return voList;
    }

    @Override
    public CustomPageAndNavigate findCinemaAllToPage(Integer pageNum, Integer pageSize) {
        List<CinemaVO> cinemas = findCinemaAll();
        return new CustomPageAndNavigate(pageNum, pageSize, cinemas);
    }

    @Override
    public CustomPageAndNavigate seachCinemaByMovieIdAndBrandAndSchedule(CinemaVO cinemaVO) {
        // 根据品牌查影院
        List<Cinema> cinemaOfBrand = new ArrayList<>();
        // 修正分页参数
        if (ObjectUtils.isEmpty(cinemaVO.getPageNum())) {
            cinemaVO.setPageNum(Constants.PageParam.PAGE_NUM);
        }
        if (ObjectUtils.isEmpty(cinemaVO.getPageSize())) {
            cinemaVO.setPageNum(Constants.PageParam.PAGE_SIZE);
        }
        // 正式搜索
        if (cinemaVO.getBrandId() == 0) {
            cinemaOfBrand = findCinemaAllByMovieId(cinemaVO.getMovieId());
        } else {
            cinemaOfBrand = findCinemaByMovieIdAanBrandId(cinemaVO.getMovieId(), cinemaVO.getBrandId());
        }
        // 根据影片ID和日程查影院
        Set<Cinema> cinemaOfDate = findCinemaByScheduleStartTimeAndMovieId(cinemaVO.getMovieId(), cinemaVO.getDateSign(), false);
        // 合并两大搜索结果
        MergeSearchResult<Cinema> mergeSearchResult = new MergeSearchResult<>();
        List<Cinema> cinemas = mergeSearchResult.inter(cinemaOfBrand, cinemaOfDate);
        if (ObjectUtils.isEmpty(cinemas)) {
            return null;
        }
        return new CustomPageAndNavigate(cinemaVO.getPageNum(), cinemaVO.getPageSize(), cinemas);
    }

    @Override
    public CinemaVO findCinemaDetailById(int cinemaId) {
        Cinema cinema = cinemaDao.findCinemaById(cinemaId);
        if (ObjectUtils.isEmpty(cinema)) {
            return null;
        }
        CinemaVO cinemaVO = new CinemaVO(cinema);
        // 找到该影院提供播放的所有影片
        if (ObjectUtils.isEmpty(cinema.getMovieIds())) {
            logger.debug(">>>>> 没有发现" + cinemaId + "号影院放映的影片");
        } else {
            List<Movie> movies = findMovieByCinema(cinema);
            cinemaVO.setMovies(movies);
        }
        // 找到该影院提供的所有服务
        List<Advantage> advantages = advantageDao.findAdvantageByCinemaId(cinemaId);
        if (ObjectUtils.isEmpty(advantages)) {
            logger.debug(">>>>> 没有发现" + cinemaId + "号影院提供的任何服务");
            return cinemaVO;
        }
        cinemaVO.setAdvantages(advantages);
        return cinemaVO;
    }

    @Override
    public List<CinemaVO> findCinemaByMovieId(Integer movieId) {
        List<Cinema> cinemaList = findCinemaAllByMovieId(movieId);
        if (ObjectUtils.isEmpty(cinemaList)) {
            return new ArrayList<>();
        }
        List<CinemaVO> voList = copyCinemaToVo(cinemaList);
        return voList;
    }

    @Override
    public List<Movie> addMovieOnCinemaDetail(CinemaVO vo) {
        // 获取已经上映的影片列表
        List<Movie> oldMovieList = vo.getMovies();
        // 获取新添加的影片ID初始列表
        List<String> newMovieIdList = vo.getNewMovieIdList();
        // 从新增影片ID初始列表中过滤掉已经上映的影片后得到的真正意义上的新增影片ID
        List<String> collectList;
        if (!ObjectUtils.isEmpty(oldMovieList)) {
            // 抽取已经上映的影片列表中的所有影片的ID
            List<Integer> oldMovieIds = TransformSearchResult.siphon(Movie::getMovieId, oldMovieList);
            // 过滤影片初始列表中已经存在的上映影片
            collectList = newMovieIdList.stream().filter(id -> {
                return !oldMovieIds.contains(Integer.valueOf(id));
            }).collect(Collectors.toList());
            // 如果筛选出来的新影片结果为空，则直接返回原已经上映的影片列表
            if (ObjectUtils.isEmpty(collectList)) {
                return oldMovieList;
            }
        } else {
            // 该影院如果不存在已经上映的影片，则直接将新增影片ID初始列表给到过滤结果集
            collectList = newMovieIdList;
        }
        // 查询影片信息并添加到上映影片列表
        for (String movieId : collectList) {
            Movie movie = movieService.findMovieById(Integer.valueOf(movieId));
            if (!ObjectUtils.isEmpty(movie)) {
                oldMovieList.add(movie);
            }
        }
        return oldMovieList;
    }

    @Override
    public List<Movie> deleteMovieOnCinemaDetail(CinemaVO vo) {
        Integer movieId = vo.getMovieId();
        Integer cinemaId = vo.getCinemaId();
        // 获取已经上映的影片列表
        List<Movie> movieList = vo.getMovies();
        // 声明场次列表，用于检查影片是否已经生成过放映计划
        List<Schedule> scheduleList = null;
        // 如果影院ID已经被定义为不存在的ID，则忽略检查计划（一般是服务于影院添加模块）
        if (cinemaId != Constants.NOT_EXIST_ID) {
            // 根据影片和影院查询影片放映计划，通过查询结果判断影片是否已经生成过放映计划
            scheduleList = findScheduleByMovieIdAndCinemaId(movieId, cinemaId);
        }
        if (ObjectUtils.isEmpty(scheduleList)) {
            return movieList.stream().filter(movie -> {
                return !movie.getMovieId().equals(movieId);
            }).collect(Collectors.toList());
        }
        return movieList;
    }

    @Override
    public int addCinema(CinemaVO vo) {
        Cinema newCinema = copyVoToCinema(vo);
        // 将数据保存到数据库
        int resp = cinemaDao.addCinema(newCinema);
        return resp;
    }

    @Override
    public int updateCinema(CinemaVO vo) {
        Cinema newCinema = copyVoToCinema(vo);
        // 将数据更新到数据库
        int resp = cinemaDao.updateCinema(newCinema);
        return resp;
    }

    @Override
    public CustomPageAndNavigate findHallAll(Integer pageNum, Integer pageSize) {
        List<Hall> hallList = hallDao.findHallAll();
        List<HallVO> halls = new ArrayList<>();
        // 将影厅实体信息拷贝到影厅模型
        hallList.forEach(hall -> {
            HallVO hallVO = new HallVO();
            BeanUtils.copyProperties(hall, hallVO);
            // 查询影院名称并封装
            Cinema cinema = cinemaDao.findCinemaById(hallVO.getCinemaId());
            hallVO.setCinemaName(cinema.getCinemaName());
            halls.add(hallVO);
        });
        return new CustomPageAndNavigate(pageNum, pageSize, halls);
    }

    @Override
    public List<Hall> findHallByCinemaId(Integer cinemaId) {
        return hallDao.findHallByCinemaId(cinemaId);
    }

    @Override
    public HallVO findHallDetailById(int hallId) {
        Hall hall = hallDao.findHallById(hallId);
        if (ObjectUtils.isEmpty(hall)) {
            return null;
        }
        // 根据影厅的基本信息查询影厅详情
        HallVO hallVO = findHallToVO(hall);
        return hallVO;
    }

    @Override
    public int addHall(HallVO vo) {
        // 提取影厅的布局信息
        List<Layout> layoutList = vo.getLayout();
        // 拷贝影厅的基本信息
        Hall hall = new Hall();
        BeanUtils.copyProperties(vo, hall);
        // 保存影厅信息
        int result = hallDao.addHall(hall);
        // 影厅基本信息保存成功后拿到自增的ID，并保存到影厅的布局表
        if (result > 0) {
            layoutList.forEach(layout -> {
                layout.setHallId(hall.getHallId());
                layoutDao.addLayout(layout);
            });
        }
        return result;
    }

    @Override
    public int updateHall(HallVO vo) {
        List<Layout> layoutList = vo.getLayout();
        // 添加新的影厅布局信息，如果需要的话
        if (!ObjectUtils.isEmpty(layoutList)) {
            layoutList.forEach(layout -> {
                layout.setHallId(vo.getHallId());
                layoutDao.addLayout(layout);
            });
        }
        // 修改影厅的基本信息
        Hall hall = new Hall();
        BeanUtils.copyProperties(vo, hall);
        int result = hallDao.updateHall(hall);
        return result;
    }

    @Override
    public int removeLayoutByHallId(Integer hallId) {
        // 清理使用了该布局信息的其他关联数据;无法清理时将导致删除失败
        boolean clearResult = clearOldDataForHallId(hallId);
        if (clearResult) {
            // 重置影厅的容量
            resetHallCapacity(hallId);
            // 删除影厅布局和有关该影厅已经结束的所有日程
            layoutDao.deleteLayoutByHallId(hallId);
            scheduleDao.deleteScheduleByHallId(hallId);
            return Constants.ResponseDataState.RESP_SUCCESS;
        }
        return Constants.ResponseDataState.DATA_IS_OCCUPIED;
    }

    @Override
    public List<HallType> findHallTypeAll() {
        return hallTypeDao.findHallTypeAll();
    }

    @Override
    public CustomPageAndNavigate findScheduleAll(Integer pageNum, Integer pageSize, String condition) {
        List<Schedule> scheduleList = scheduleDao.findScheduleAll();
        // 根据条件筛选日程
        scheduleList = filterScheduleByCondition(scheduleList, condition);
        List<ScheduleVO> voList = findScheduleToVO(scheduleList);
        return new CustomPageAndNavigate(pageNum, pageSize, voList);
    }

    @Override
    public ScheduleVO findScheduleDetailById(int scheduleId) {
        Schedule schedule = scheduleDao.findScheduleById(scheduleId);
        if (ObjectUtils.isEmpty(schedule)) {
            return null;
        }
        ScheduleVO scheduleVO = findScheduleToVO(schedule);
        return scheduleVO;
    }

    @Override
    public List<ScheduleVO> findScheduleDetailsByMovieIdAndCinemaIdAndStartDate(int movieId, int cinemaId, int dateSign) {
        List<ScheduleVO> scheduleVOList = new ArrayList<>();
        // 根据影片ID和影院ID查询所有的场次信息（日程）
        List<Schedule> schedules = findScheduleByMovieIdAndCinemaId(movieId, cinemaId);
        // 筛选符合用户选中的日期的场次信息
        List<Schedule> screenSchedules = screenScheduleByScheduleStartTime(dateSign, schedules);
        // 将场次信息包装成场次详情信息
        if (ObjectUtils.isEmpty(screenSchedules)) {
            return scheduleVOList;
        }
        screenSchedules.forEach(schedule -> {
            ScheduleVO details = findScheduleToVO(schedule);
            scheduleVOList.add(details);
        });
        return scheduleVOList;
    }

    @Override
    public CustomPageAndNavigate searchScheduleByMovieNameToPage(Integer pageNum, Integer pageSize, String keyword, String condition) {
        List<Movie> movieByName = movieDao.findMovieByName(keyword);
        if (ObjectUtils.isEmpty(movieByName)) {
            return null;
        }
        List<Schedule> scheduleList = new ArrayList<>();
        for (Movie movie : movieByName) {
            List<Schedule> scheduleByMovie = scheduleDao.findScheduleByMovieId(movie.getMovieId());
            scheduleList.addAll(scheduleByMovie);
        }
        // 根据条件筛选日程
        scheduleList = filterScheduleByCondition(scheduleList, condition);
        List<ScheduleVO> voList = findScheduleToVO(scheduleList);
        return new CustomPageAndNavigate(pageNum, pageSize, voList);
    }

    @Override
    public int addSchedule(ScheduleVO vo) {
        // 手动处理日期，防止时间精度丢失
        String dateTime = vo.getDateTime();
        DateTime scheduleStartTime = DateUtil.parse(dateTime);
        vo.setScheduleStartTime(scheduleStartTime);
        // 判断当前影厅是否仍然存在当前影片未结束的放映计划
        boolean isExistOnHall = scheduleIsExistOnHall(vo.getMovieId(), vo.getHallId(), scheduleStartTime);
        if (isExistOnHall) {
            return Constants.ResponseDataState.UPDATE_FAIL_EXIST;
        }
        Schedule schedule = new Schedule();
        BeanUtils.copyProperties(vo, schedule);
        scheduleDao.insterScheduleFromUpdateSchedule(schedule);
        return Constants.ResponseDataState.RESP_SUCCESS;
    }

    @Override
    public boolean deleteSchedule(Integer scheduleId) {
        // 检查当前计划是否已经创建了订单，如果没有创建订单才允许取消计划
        List<Orderinfo> orderBySchedule = orderDao.findOrderByScheduleId(scheduleId);
        if (ObjectUtils.isEmpty(orderBySchedule)) {
            scheduleDao.removeScheduleById(scheduleId,Constants.INERT_DATA);
            return true;
        }
        return false;
    }

    @Override
    public void updateScheduleByDateAndMovieId(List<Integer> movieIds, Date planTime) {
        if (ObjectUtils.isEmpty(movieIds)) {
            logger.debug(">>>>> 没有检查到电影信息，无法执行更新计划，请确认后重试");
        }
        SetOperations setOperations = redisTemplate.opsForSet();
        // 设置影片的语言版本
        Integer versionId = 1;
        // 计算计划日期距离今天的偏移量
        Integer timeOffset = countDateOffset(planTime);
        // 根据影片ID更新有放映该影片的所有影院的有关该影片的播放计划
        for (Integer movieId : movieIds) {
            List<Cinema> cinemas = findCinemaAllByMovieId(movieId);
            for (Cinema cinema : cinemas) {
                List<Hall> hallList = hallDao.findHallByCinemaId(cinema.getCinemaId());
                if (!ObjectUtils.isEmpty(hallList)) {
                    // 放映时间随机列表初始化
                    initRandomTimeToRedis(DateUtil.formatDate(planTime));
                    // 影厅随机列表初始化
                    initRandomIdToRedis(hallList);
                    // 根据随机参数创建一份放映计划
                    int hallId = (Integer) setOperations.pop(Constants.ScheduleData.RANDOM_ID_KEY);
                    String showtime = (String) setOperations.pop(Constants.ScheduleData.RANDOM_TIME_KEY);
                    Date scheduleStartTime = DateUtil.parseDateTime(showtime);
                    double schedulePrice = cinema.getStartPurchasePrice();
                    Schedule schedule = createUpdateSchedule(hallId, movieId, versionId, scheduleStartTime, schedulePrice);
                    // 清空随机列表
                    clearRandomTimeAndIdFromRedis();
                    // 判断当期计划中的影院是否已经安排了该影片的放映，如果没有则更新计划
                    Set<Cinema> cinemaSet = findCinemaByScheduleStartTimeAndMovieId(movieId, timeOffset, true);
                    boolean isExistOnList = cinemaIsExistOnList(cinema.getCinemaId(), cinemaSet);
                    if (isExistOnList) {
                        logger.debug("<<<<<" + cinema.getCinemaId() + "号影院已经存在" + movieId + "号影片的播放计划，跳过此次更新任务");
                    } else {
                        scheduleDao.insterScheduleFromUpdateSchedule(schedule);
                    }
                } else {
                    logger.debug(">>>>> 没有检查到" + cinema.getCinemaId() + "号影院的任何放映厅信息，无法生成任何播放计划");
                }
            }
        }
    }

    @Override
    public int deleteOldSchedule() {
        List<Schedule> scheduleList = scheduleDao.findScheduleAll();
        if (ObjectUtils.isEmpty(scheduleList)) {
            return 0;
        }
        int total = 0;
        for (Schedule schedule : scheduleList) {
            Date scheduleStartTime = schedule.getScheduleStartTime();
            // 计算订单从创建到现在的时间差（单位：分钟）
            long timeDifference = DateUtil.between(scheduleStartTime, new Date(), DateUnit.DAY);
            if (timeDifference > Constants.ScheduleData.RESERVE_EFFECTIVE_TIME) {
                Integer scheduleId = schedule.getScheduleId();
                logger.debug("<<<<< " + scheduleId + "号计划被检查已经超过30天；放映时间：" + DateUtil.formatDateTime(scheduleStartTime));
                scheduleDao.deleteScheduleById(scheduleId);
                total++;
            }
        }
        return total;
    }

    /**
     * 从详情类中拷贝信息到实体类。如果存在影片列表。将会自动把影片列表转成影片ID列表。因为不考虑详情
     * 类为空的情况，所以可能存在空指针
     */
    private Cinema copyVoToCinema(CinemaVO vo) {
        Cinema newCinema = new Cinema();
        BeanUtils.copyProperties(vo, newCinema);
        List<Movie> movieList = vo.getMovies();
        if (!ObjectUtils.isEmpty(movieList)) {
            // 提取所有上映的影片ID
            List<Integer> siphon = TransformSearchResult.siphon(Movie::getMovieId, movieList);
            // 将影片ID转成字符串格储存
            List<String> collect = siphon.stream().map(id -> String.valueOf(id)).collect(Collectors.toList());
            newCinema.setMovieIds(collect);
        }
        return newCinema;
    }

    /**
     * 从影院实体拷贝信息到影院详情模型。将会自动关联影院已经添加的影厅数量
     */
    private CinemaVO copyCinemaToVo(Cinema cinema) {
        CinemaVO cinemaVO = new CinemaVO();
        BeanUtils.copyProperties(cinema, cinemaVO);
        // 查询该影院包含的影厅总数量
        List<Hall> hallByCinema = hallDao.findHallByCinemaId(cinema.getCinemaId());
        cinemaVO.setHallTotal(hallByCinema.size());
        return cinemaVO;
    }

    /**
     * 从影院实体列表拷贝信息到影院详情模型列表。因为不考虑实体列表为空，所以可能出现空指针
     */
    private List<CinemaVO> copyCinemaToVo(List<Cinema> cinemaList) {
        List<CinemaVO> cinemas = new ArrayList<>();
        // 将影院实体信息拷贝到影院模型
        cinemaList.forEach(cinema -> {
            CinemaVO cinemaVO = copyCinemaToVo(cinema);
            cinemas.add(cinemaVO);
        });
        return cinemas;
    }

    /**
     * 计算指定日期距离今天的偏移量
     *
     * @param planTime 需要计算的计划时间
     */
    private int countDateOffset(Date planTime) {
        Integer timeOffset = 0;
        Date today = DateUtil.parse(DateUtil.today());
        long timeDifference = DateUtil.between(today, planTime, DateUnit.DAY);
        if (today.before(planTime)) {
            timeOffset = (int) timeDifference;
        } else {
            timeOffset = -(int) timeDifference;
        }
        return timeOffset;
    }

    /**
     * 重置影厅的容量为初始容量
     *
     * @param hallId 待重置容量的影厅
     * @return 重置结果
     */
    private boolean resetHallCapacity(Integer hallId) {
        Hall hall = new Hall();
        hall.setHallId(hallId);
        hall.setHallCapacity(Constants.HallData.INITIAL_CAPACITY);
        int result = hallDao.updateHallCapacity(hall);
        if (result > 0) {
            return true;
        }
        return false;
    }

    /**
     * 根据影厅的ID清理所有关联的旧数据（主要指用户订单），如果存在未结束的放映计划或未过期的订单
     * 还在使用该布局，则清理失败
     *
     * @return 返回清理结果
     */
    private boolean clearOldDataForHallId(Integer hallId) {
        // 根据影厅的ID查询所有的排场
        List<Schedule> scheduleByHall = scheduleDao.findScheduleByHallId(hallId);
        if (ObjectUtils.isEmpty(scheduleByHall)) {
            return true;
        }
        // 检查排场列表中是否存在未结束的计划
        Schedule[] schedules = scheduleByHall.toArray(new Schedule[scheduleByHall.size()]);
        boolean exist = !examineEndSchedule(schedules);
        if (exist) {
            return !exist;
        }
        // 清理所有的旧数据
        scheduleByHall.forEach(schedule -> {
            orderDao.deleteByScheduleId(schedule.getScheduleId());
        });
        return true;
    }

    /**
     * 检查排场是否已经结束
     *
     * @param schedules 需要检查的可变数量的日程信息
     * @return 检查结果。当全部的日程都结束时，返回true；但只要存在一个未结束的日程就返回false
     */
    private boolean examineEndSchedule(Schedule... schedules) {
        for (Schedule schedule : schedules) {
            // 日程开始时间
            Date scheduleStartTime = schedule.getScheduleStartTime();
            // 当前时间
            Date nowTime = new Date();
            if (nowTime.before(scheduleStartTime)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 根据现有信息创建更新计划
     */
    private Schedule createUpdateSchedule(int hallId, int movieId, int versionId, Date scheduleStartTime, double schedulePrice) {
        Schedule schedule = new Schedule();
        schedule.setHallId(hallId);
        schedule.setMovieId(movieId);
        schedule.setVersionId(versionId);
        schedule.setScheduleStartTime(scheduleStartTime);
        schedule.setSchedulePrice(schedulePrice);
        return schedule;
    }

    /**
     * 初始化场次时间的随机列表(临时存在 键"update:schedule:hall:pop:time")
     */
    private void initRandomTimeToRedis(String today) {
        // 获取集合类型的操作模板
        SetOperations setOperations = redisTemplate.opsForSet();
        // 上午的开场时间
        String morningShowTime = today + " 9:15:00";
        setOperations.add(Constants.ScheduleData.RANDOM_TIME_KEY, morningShowTime);
        // 下午的开场时间
        String afternoonShowTime = today + " 14:30:00";
        setOperations.add(Constants.ScheduleData.RANDOM_TIME_KEY, afternoonShowTime);
        // 晚上的开场时间
        String nightShowTime = today + " 20:45:00";
        setOperations.add(Constants.ScheduleData.RANDOM_TIME_KEY, nightShowTime);
        // 设置60秒的过期时间
        redisTemplate.expire(Constants.ScheduleData.RANDOM_TIME_KEY, 5, TimeUnit.MINUTES);
    }

    /**
     * 初始化影厅的随机列表（临时存在 键"update:schedule:hall:pop:id"）
     */
    private void initRandomIdToRedis(List<Hall> hallList) {
        // 获取集合类型的操作模板
        SetOperations setOperations = redisTemplate.opsForSet();
        hallList.forEach(hall -> {
            setOperations.add(Constants.ScheduleData.RANDOM_ID_KEY, hall.getHallId());
        });
        redisTemplate.expire(Constants.ScheduleData.RANDOM_ID_KEY, 5, TimeUnit.MINUTES);
    }

    /**
     * 清空随机列表
     */
    private void clearRandomTimeAndIdFromRedis() {
        // 获取集合类型的操作模板
        SetOperations setOperations = redisTemplate.opsForSet();
        // 清空ID列表
        Long randomIdSize = setOperations.size(Constants.ScheduleData.RANDOM_ID_KEY);
        for (int i = 0; i < randomIdSize; i++) {
            setOperations.pop(Constants.ScheduleData.RANDOM_ID_KEY);
        }
        // 清空时间列表
        Long randomTimeSize = setOperations.size(Constants.ScheduleData.RANDOM_TIME_KEY);
        for (int i = 0; i < randomTimeSize; i++) {
            setOperations.pop(Constants.ScheduleData.RANDOM_TIME_KEY);
        }
    }

    /**
     * 查询单个影厅的详细信息
     */
    private HallVO findHallToVO(Hall hall) {
        HallVO hallVO = new HallVO(hall);
        // 影厅类型
        Integer hallTypeId = hall.getHallTypeId();
        HallType hallType = null;
        if (!ObjectUtils.isEmpty(hallTypeId)) {
            hallType = hallTypeDao.findHallTypeById(hallTypeId);
        }
        if (!ObjectUtils.isEmpty(hallType)) {
            hallVO.setHallType(hallType);
        }
        // 影厅布局
        List<Layout> layout = layoutDao.findLayoutByHallId(hall.getHallId());
        if (!ObjectUtils.isEmpty(layout)) {
            hallVO.setLayout(layout);
        }
        return hallVO;
    }

    /**
     * 根据影院ID和影片ID查询所有的日程安排
     */
    private List<Schedule> findScheduleByMovieIdAndCinemaId(int movieId, int cinemaId) {
        List<Schedule> schedules = new ArrayList<>();
        // 获取该影院下的所有影厅信息
        List<Hall> halls = hallDao.findHallByCinemaId(cinemaId);
        if (ObjectUtils.isEmpty(halls)) {
            return schedules;
        }
        // 根据影厅ID和影片ID查询所有的日程
        halls.forEach(hall -> {
            List<Schedule> scheduleList = scheduleDao.findScheduleByMovieIdAndHallId(movieId, hall.getHallId());
            schedules.addAll(scheduleList);
        });
        return schedules;
    }

    /**
     * 根据选中的观影日期筛选场次信息
     */
    private List<Schedule> screenScheduleByScheduleStartTime(int dateSign, List<Schedule> schedules) {
        List<Schedule> screenResult = new ArrayList<>();
        if (ObjectUtils.isEmpty(schedules)) {
            return screenResult;
        }
        // 将日期标记转换成日期
        String selectStartDate = switchDateSignToDate(dateSign);
        // 根据日期筛选场次，如果是用户选中的日期就添加到集合
        schedules.forEach(schedule -> {
            if (DateUtil.formatDate(schedule.getScheduleStartTime()).equals(selectStartDate)) {
                screenResult.add(schedule);
            }
        });
        return screenResult;
    }

    /**
     * 查询单个场次的详细信息；封装影片信息时会忽略影片的删除状态（考虑到历史订单或片场也应该保证数据的显示正常）
     */
    private ScheduleVO findScheduleToVO(Schedule schedule) {
        ScheduleVO scheduleVO = new ScheduleVO();
        scheduleVO.setSchedule(schedule);
        // 记录场次的开始日期
        scheduleVO.setStartDate(DateUtil.formatDate(schedule.getScheduleStartTime()));
        // 影厅信息
        Hall hall = hallDao.findHallById(schedule.getHallId());
        scheduleVO.setHall(hall);
        // 影厅类型
        Integer hallTypeId = hall.getHallTypeId();
        if (!ObjectUtils.isEmpty(hallTypeId)) {
            HallType hallType = hallTypeDao.findHallTypeById(hallTypeId);
            scheduleVO.setHallType(hallType);
        }
        // 售出座位标记
        if (!ObjectUtils.isEmpty(hall)) {
            HallVO haveSeatsVO = markSeat(findHallToVO(hall), schedule);
            scheduleVO.setSeats(haveSeatsVO);
        }
        // 影院信息
        Cinema cinema = cinemaDao.findCinemaById(findCinemaByScheduleId(schedule.getScheduleId()).getCinemaId());
        scheduleVO.setCinema(cinema);
        // 影片的语言版本
        LanguageVersion version = versionDao.findVersionById(schedule.getVersionId());
        scheduleVO.setVersion(version);
        // 将场次开始时间转换成放映开始时间和放映结束时间 如：2021-11-29 13:00:00 -> 13:00
        Movie movie = movieDao.findMovieByIdNeedIgnoreState(schedule.getMovieId());
        int movieDuration = 0;
        if (ObjectUtils.isEmpty(movie)) {
            return scheduleVO;
        }
        // 获取影片时长
        if (!ObjectUtils.isEmpty(movie.getMovieDuration())) {
            movieDuration = Integer.valueOf(movie.getMovieDuration().substring(0, movie.getMovieDuration().length() - 2));
            scheduleVO.setMovieDuration(movieDuration);
        }
        // 提取放映开始时间和放映结束时间
        Map<String, String> timeMap = extractShowAndEndTime(schedule.getScheduleStartTime(), movieDuration);
        scheduleVO.setStartTime(timeMap.get(Constants.ScheduleData.SHOW_TIME_KEY));
        scheduleVO.setEndTime(timeMap.get(Constants.ScheduleData.END_TIME_KEY));
        // 记录影片的名称
        scheduleVO.setMovieCnName(movie.getMovieCnName());
        // 更正片场状态
        scheduleVO = correctScheduleState(scheduleVO);
        return scheduleVO;
    }

    /**
     * 将基本日程列表转化成日程详情列表
     */
    private List<ScheduleVO> findScheduleToVO(List<Schedule> scheduleList) {
        List<ScheduleVO> voList = new ArrayList<>();
        if (ObjectUtils.isEmpty(scheduleList)) {
            return voList;
        }
        scheduleList.forEach(schedule -> {
            ScheduleVO scheduleToVO = findScheduleToVO(schedule);
            voList.add(scheduleToVO);
        });
        return voList;
    }

    /**
     * 检查并更正片场状态
     */
    private ScheduleVO correctScheduleState(ScheduleVO scheduleVO) {
        if (ObjectUtils.isEmpty(scheduleVO) || ObjectUtils.isEmpty(scheduleVO.getSchedule())) {
            return scheduleVO;
        }
        // 计算影片的结束时间
        DateTime showDateTime = DateTime.of(scheduleVO.getSchedule().getScheduleStartTime());
        DateTime endDateTime = DateUtil.offsetMinute(showDateTime, scheduleVO.getMovieDuration());
        Date nowTime = new Date();
        // 片场结束
        if (endDateTime.before(nowTime)) {
            scheduleVO.setScheduleState(Constants.ScheduleData.END_SHOW);
            return scheduleVO;
        }
        // 片场已经开始
        if (showDateTime.before(nowTime)) {
            scheduleVO.setScheduleState(Constants.ScheduleData.SHOWING);
            return scheduleVO;
        }
        return scheduleVO;
    }

    /**
     * 根据场次信息处理影厅的座位布局（标记被售空的座位）
     */
    public HallVO markSeat(HallVO hallVO, Schedule schedule) {
        // 获取当前场次已经被售出座位的定位符号
        List<String> seatIds = schedule.getSeatIds();
        if (ObjectUtils.isEmpty(seatIds)) {
            // 没有已经被售出的座位则直接结束
            return hallVO;
        }
        // 将座位的定位符号解析成行列索引，并更新布局中的座位标记 如：1~2 表示 第一行第二列
        seatIds.forEach(id -> {
            // 获取到行的索引位
            int rowIndex = Integer.valueOf(id.split(Constants.DATA_JOINER)[0]);
            // 获取到列的索引位
            int columnIndex = Integer.valueOf(id.split(Constants.DATA_JOINER)[1]);
            // 标记已经被售出的座位
            List<Layout> layouts = hallVO.getLayout();
            if (!ObjectUtils.isEmpty(layouts)) {
                layouts.forEach(layout -> {
                    // 拿到每排的所有座位信息
                    List<String> seatStateIds = layout.getSeatStateIds();
                    // 如果座位的行号对应，就将已经售出的座位标记为售出状态
                    if (layout.getSeatColumnId() == rowIndex) {
                        seatStateIds.set(columnIndex - 1, Constants.SeatLayoutData.SOLD_SEAT);
                    }
                    layout.setSeatStateIds(seatStateIds);
                });
            } else {
                logger.debug(hallVO.getHall().getHallId() + "号影厅没有检查到的任何座位布局信息");
            }
        });
        return hallVO;
    }

    /**
     * 从场次的开始时间中提取到电影的放映时间和结束时间 如：2021-11-29 13:00:00 -> 13:00
     */
    private Map<String, String> extractShowAndEndTime(Date scheduleStartTime, int movieDuration) {
        Map<String, String> timeMap = new HashMap<>(16);
        DateTime showDateTime = DateTime.of(scheduleStartTime);
        DateTime endDateTime = DateUtil.offsetMinute(showDateTime, movieDuration);
        // 提取到24小时制的小时数
        int showHour = showDateTime.hour(true);
        int endHour = endDateTime.hour(true);
        // 提取到分钟数
        int showMinute = showDateTime.minute();
        int endMinute = endDateTime.minute();
        // 拼接放映时间和结束时间 格式-00:00
        String showTime = showHour + ":" + showMinute;
        String endTime = endHour + ":" + endMinute;
        if (showMinute == 0) {
            showTime = showHour + ":0" + showMinute;
        }
        if (endMinute == 0) {
            endTime = endHour + ":0" + endMinute;
        }
        timeMap.put(Constants.ScheduleData.SHOW_TIME_KEY, showTime);
        timeMap.put(Constants.ScheduleData.END_TIME_KEY, endTime);
        return timeMap;
    }

    /**
     * 根据影院实体获取所有的影片信息；跳过不存的影片ID或者被标记为删除的影片
     */
    private List<Movie> findMovieByCinema(Cinema cinema) {
        List<Movie> movies = new ArrayList<>();
        // 拿到该影院提供播放的所有影片ID
        List<String> movieIds = cinema.getMovieIds();
        for (String movieId : movieIds) {
            Movie movie = movieService.findMovieById(Integer.valueOf(movieId));
            // 如果根据ID没有找到影片信息，说明该影片已经被移除或者是人为记录的错误值，则直接跳过
            if (ObjectUtils.isEmpty(movie)) {
                logger.debug("检测到" + cinema.getCinemaId() + "号影院关联了错误的影片ID：" + movieId + ";请检查该影片是否已经被移除");
                continue;
            }
            movies.add(movie);
        }
        return movies;
    }

    /**
     * 查询所有的影院并根据影片ID筛选
     */
    private List<Cinema> findCinemaAllByMovieId(Integer movieId) {
        // 查询所有的影院信息
        List<Cinema> cinemaList = cinemaDao.findCinemaAll();
        return filterCinemaByMovieId(cinemaList, movieId);
    }

    /**
     * 根据影片ID和影院品牌查询并筛选影院
     */
    private List<Cinema> findCinemaByMovieIdAanBrandId(Integer movieId, Integer brandId) {
        // 拿到对应品牌的影院信息
        List<Cinema> cinemaList = cinemaDao.findCinemaByBrandId(brandId);
        return filterCinemaByMovieId(cinemaList, movieId);
    }

    /**
     * 根据影片ID筛选影院
     */
    private List<Cinema> filterCinemaByMovieId(List<Cinema> cinemaList, Integer movieId) {
        List<Cinema> cinemas = new ArrayList<>();
        // 取出有播放该影片的电影院
        cinemaList.forEach(cinema -> {
            // 拿到该影院放映的所有影片信息，如果没有就直接结束
            List<String> movieIds = cinema.getMovieIds();
            if (ObjectUtils.isEmpty(movieIds)) {
                return;
            }
            // 如果影院有放映该影片就添加到集合中
            for (String id : movieIds) {
                if (id.equals(String.valueOf(movieId))) {
                    cinemas.add(cinema);
                }
            }
        });
        return cinemas;
    }

    /**
     * 根据影片的ID和排期查询并筛选影院。如果观影日期超过解析范围，默认会跳过筛选
     */
    private Set<Cinema> findCinemaByScheduleStartTimeAndMovieId(Integer movieId, Integer dateSign, boolean closeFindAll) {
        Set<Cinema> cinemaSet = new LinkedHashSet<>();
        // 查询有该影片安排的所有场次信息（日程）
        List<Schedule> schedules = scheduleDao.findScheduleByMovieId(movieId);
        // 解析用户选中的观影日期
        String selectStartDate = switchDateSignToDate(dateSign);
        // 根据用户选中的观影日期找到其归属的影院信息
        schedules.forEach(schedule -> {
            Date scheduleStartTime = schedule.getScheduleStartTime();
            /* 如果观影日期解析为空并且没有关闭查询所有数据；或者观影日期与场次信息中的开始时间相同就查找影院信息
             * 查找条件1：观影日期与场次信息中的开始时间相同。即：根据选中的开始时间筛选对应的影院
             * 查询条件2：观影日期解析为空并且没有关闭查询所有数据。即：查询所有的影院信息
             * */
            if (!ObjectUtils.isEmpty(selectStartDate)) {
                if (DateUtil.formatDate(scheduleStartTime).equals(selectStartDate)) {
                    Cinema cinema = findCinemaByScheduleId(schedule.getScheduleId());
                    cinemaSet.add(cinema);
                }
            } else {
                if (!closeFindAll) {
                    Cinema cinema = findCinemaByScheduleId(schedule.getScheduleId());
                    cinemaSet.add(cinema);
                }
            }
        });
        return cinemaSet;
    }

    /**
     * 按搜索条件筛选日程(上映或者下架)。以日程中的开始时间为准，开始时间在当前时间之前表示已下架；开始时间在当前时间之后表示上映
     * 如果搜索条件为空将会跳过筛选，直接返回原本的结果集
     *
     * @param scheduleList    需要筛选的日程列表
     * @param searchCondition 筛选条件
     * @return 返回过滤后的结果集；如果传递的结果集为空的话，那么将会被直接返回，不处理。
     */
    private List<Schedule> filterScheduleByCondition(List<Schedule> scheduleList, String searchCondition) {
        if (ObjectUtils.isEmpty(scheduleList) || ObjectUtils.isEmpty(searchCondition)) {
            return scheduleList;
        }
        if (searchCondition.equals(Constants.ON_LINE)) {
            return scheduleList.stream().filter(schedule -> {
                Date scheduleStartTime = schedule.getScheduleStartTime();
                return scheduleStartTime.after(new Date());
            }).collect(Collectors.toList());
        }
        if (searchCondition.equals(Constants.OFF_LINE)) {
            return scheduleList.stream().filter(schedule -> {
                Date scheduleStartTime = schedule.getScheduleStartTime();
                return scheduleStartTime.before(new Date());
            }).collect(Collectors.toList());
        }
        return scheduleList;
    }

    /**
     * 根据排场（场次ID）找影院
     */
    private Cinema findCinemaByScheduleId(int scheduleId) {
        // 找场次
        Schedule schedule = scheduleDao.findScheduleById(scheduleId);
        if (ObjectUtils.isEmpty(schedule)) {
            return null;
        }
        // 找影厅
        Hall hall = hallDao.findHallById(schedule.getHallId());
        if (ObjectUtils.isEmpty(hall)) {
            return null;
        }
        // 找影院
        Cinema cinema = cinemaDao.findCinemaById(hall.getCinemaId());
        return cinema;
    }

    /**
     * 根据日期的标志定位到真实有效的时间 如：2021/9/9
     * -1:固定在指定日期 0:今天 1:明天 2:后天
     */
    private String switchDateSignToDate(Integer dateSign) {
        DateTime date = DateUtil.date();
        switch (dateSign) {
            case -1:
                date = DateUtil.parse(Constants.ScheduleData.DEVELOP_TEST_TIME);
                return DateUtil.formatDate(date);
            case 0:
                return DateUtil.today();
            case 1:
                // 从当前时间往后偏移1天
                date = DateUtil.offsetDay(date, Constants.ScheduleData.THE_SECOND_DAY);
                return DateUtil.formatDate(date);
            case 2:
                date = DateUtil.offsetDay(date, Constants.ScheduleData.THE_THIRD_DAY);
                return DateUtil.formatDate(date);
            default:
                return null;
        }
    }

    /**
     * 判断影院列表中是否存在指定影院
     *
     * @param cinemaId  要查找的影院ID
     * @param cinemaSet 要查找的影院列表
     * @return 判断结果
     */
    private boolean cinemaIsExistOnList(Integer cinemaId, Set<Cinema> cinemaSet) {
        if (ObjectUtils.isEmpty(cinemaSet)) {
            return false;
        }
        for (Cinema cinema : cinemaSet) {
            if (cinema.getCinemaId().equals(cinemaId)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 判断目标日程在指定影厅中是否已经存在还没有结束的放映计划
     *
     * @param movieId  计划中的影片
     * @param hallId   计划中的影厅
     * @param planTime 计划的时间
     * @return 是否已经存在
     */
    private boolean scheduleIsExistOnHall(Integer movieId, Integer hallId, Date planTime) {
        List<Schedule> scheduleList = scheduleDao.findScheduleByMovieIdAndHallId(movieId, hallId);
        if (ObjectUtils.isEmpty(scheduleList)) {
            return false;
        }
        for (Schedule schedule : scheduleList) {
            Date scheduleStartTime = schedule.getScheduleStartTime();
            if (scheduleStartTime.after(planTime)) {
                return true;
            }
        }
        return false;
    }
}
